Skip to content

fix(blob): infer callback URL from node headers#1044

Open
matingathani wants to merge 1 commit intovercel:mainfrom
matingathani:fix/blob-handle-upload-callback-url
Open

fix(blob): infer callback URL from node headers#1044
matingathani wants to merge 1 commit intovercel:mainfrom
matingathani:fix/blob-handle-upload-callback-url

Conversation

@matingathani
Copy link
Copy Markdown
Contributor

Summary

Fixes #874

When handleUpload() is used with onUploadCompleted() and no explicit
callbackUrl is returned from onBeforeGenerateToken(), the SDK should still
be able to infer a callback URL for Node handlers from public request headers.

This change adds a fallback that uses x-forwarded-host,
x-forwarded-proto, and host to infer the callback URL before falling back to
Vercel environment variables only.

Changes

  • infer callback URLs from Node request headers when onUploadCompleted is set
  • prefer forwarded host/proto headers when present
  • default localhost callbacks to http
  • add regression tests for forwarded-host and localhost Node handler flows
  • add a patch changeset for @vercel/blob

Test Plan

  • pnpm -C packages/blob run test:node
  • verified forwarded host/proto headers produce the expected callback URL
  • verified localhost Node requests default to http

Copilot AI review requested due to automatic review settings April 2, 2026 21:56
@vercel
Copy link
Copy Markdown
Contributor

vercel bot commented Apr 2, 2026

@matingathani is attempting to deploy a commit to the Curated Tests - Permanent E2E Team on Vercel.

A member of the Team first needs to authorize it.

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Apr 2, 2026

🦋 Changeset detected

Latest commit: ae65cd2

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
@vercel/blob Patch
vercel-storage-integration-test-suite Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a Node-specific fallback to infer onUploadCompleted callback URLs from request headers when onBeforeGenerateToken() doesn’t return an explicit callbackUrl, addressing cases where callbacks weren’t firing off-Vercel.

Changes:

  • Infer callback URL from Node request headers (x-forwarded-host, x-forwarded-proto, host) when needed.
  • Default inferred protocol to http for localhost hosts.
  • Add regression tests for forwarded-host/proto and localhost Node request flows, plus a patch changeset.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
packages/blob/src/client.ts Adds header-based callback URL inference helpers and integrates them into getCallbackUrl().
packages/blob/src/client.node.test.ts Adds tests validating forwarded header precedence and localhost protocol inference.
.changeset/fix-blob-handle-upload-callback-url.md Publishes a patch changeset describing the fix.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

function hasHeadersGet(
headers: IncomingMessage['headers'] | Headers | undefined,
): headers is Headers {
return typeof headers === 'object' && headers !== null && 'get' in headers;
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hasHeadersGet() can return a false positive when the Node IncomingMessage.headers object contains a header named get (header names are lowercased), because 'get' in headers will then be true but headers.get will be a string/string[]. This would cause a runtime TypeError when calling request.headers.get(...). Consider tightening the guard to check that get is actually a function (e.g., typeof (headers as any).get === 'function') before treating it as a Headers instance.

Suggested change
return typeof headers === 'object' && headers !== null && 'get' in headers;
return (
typeof headers === 'object' &&
headers !== null &&
typeof (headers as { get?: unknown }).get === 'function'
);

Copilot uses AI. Check for mistakes.
}

function inferProtocol(host: string): 'http' | 'https' {
return host.startsWith('localhost') || host.startsWith('127.0.0.1')
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

inferProtocol() uses startsWith('localhost') / startsWith('127.0.0.1'), which will misclassify non-local hosts like localhost.example.com as http, and it doesn’t cover common local hosts like [::1] (IPv6 loopback). Consider parsing out the hostname (strip port / brackets) and doing exact matches against known loopback names/addresses (e.g., localhost, 127.0.0.1, ::1, maybe 0.0.0.0) before defaulting to https.

Suggested change
return host.startsWith('localhost') || host.startsWith('127.0.0.1')
const normalizedHost = host.trim().toLowerCase();
let hostname: string;
if (normalizedHost.startsWith('[')) {
const closingBracketIndex = normalizedHost.indexOf(']');
hostname =
closingBracketIndex === -1
? normalizedHost.slice(1)
: normalizedHost.slice(1, closingBracketIndex);
} else {
hostname = normalizedHost.split(':', 1)[0] ?? normalizedHost;
}
return hostname === 'localhost' ||
hostname === '127.0.0.1' ||
hostname === '::1' ||
hostname === '0.0.0.0'

Copilot uses AI. Check for mistakes.
@matingathani
Copy link
Copy Markdown
Contributor Author

A note on the fallback order here: VERCEL_BLOB_CALLBACK_URL still takes precedence as the explicit override.Header-based inference only runs when no callback URL was returned from onBeforeGenerateToken() and no env override is set. The goal is to make Node handlers work in proxy / tunnel / staging setups where the public callback URL is already represented by forwarded request headers.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

@vercel/blob onUploadComplete does not trigger

2 participants